import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.PrintWriter;
import java.net.Socket;
import java.net.UnknownHostException;

/**
* class ClientReadThread:
*   - input parameters: socket to server, player name
*   - initialize client write thread for receive keyboard command
*   - receive and parse messages from server
*   - update (local mirror) maze with data from server
*   - update player location, treasure with data from server 
*/
 
public class ClientReadThread extends Thread {
	private Socket socket = null;
	private Socket bsSocket = null;
	private String clientID;
	private Player player;
	private PrintWriter os;	// primary server's os
	private PrintWriter bsos; // backup server's os
	private boolean serverClient; // true: this is a PS-client or BS-client
	private Maze maze;
	
	public ClientReadThread(Socket socket, Player player) throws IOException {
		this.socket = socket;
		this.player = player;
		this.os = new PrintWriter(socket.getOutputStream());
		this.serverClient = false;
	}
	
	public void run() {
		boolean running = true;
		BufferedReader is = null;
		
		// start the Client Write Thread
		ClientWriteThread writeThread = new ClientWriteThread();
		writeThread.start();
		
		while (socket.isConnected()) {
			try {
				is = new BufferedReader(new InputStreamReader(socket.getInputStream()));
				writeThread.setOS(os);
				String response = "";
				
				/* To receive and parse messages from Server		   
				*/
				
				while (running) {
					response = is.readLine();
					while (response != null) { 
						analyze(response);
						response = is.readLine();
					}
				}
				
			} catch (IOException e) {
				//e.printStackTrace();
			} finally {
				try {
					is.close(); is = null;
					os.close(); os = null;
					socket.close(); socket = null;
					
					os = bsos;
					socket = bsSocket;
					
					if ( !serverClient ) {
						os.println("backup-apply");
						os.flush();
					}
					//System.out.println("PS down, change os and socket");
				} catch (IOException e) {
					//e.printStackTrace();
				}
			}
		}
	}

	private void analyze(String response) {
		String str[] = response.split("\\s+");
		
		if (str[0].equals("treasure-info")|| str[0].equals("set-player"))
		{
			System.out.println("Client > ");
		}
		else
		{
			System.out.println("Client > " + response);
		}
		
		// set-id ID N M
		if (str[0].equals("set-id")) {
			clientID = str[1];
			maze = new Maze(Integer.parseInt(str[2]), Integer.parseInt(str[3]));
			
			os.println("born " + player.getName());
			os.flush();
			
			os.println("sync " + TimeThread.getTime());
			os.flush();
			
			if (clientID.equals("0")) {
				serverClient = true;
			}
		}
		
		// sync CTIME STIME
		else if (str[0].equals("sync")) {
			int nowTime = TimeThread.getTime();
			int oldTime = Integer.parseInt(str[1]);
			int sTime = Integer.parseInt(str[2]);
			TimeThread.setTime(sTime + (nowTime - oldTime) / 2);
		}
		
		// treasure-info [x1 y1 num1 ...]
		else if (str[0].equals("treasure-info")) {
			int treasureNum = str.length;
			for (int i = 1; i < treasureNum; i = i + 3) {
				maze.setTreasure(Integer.parseInt(str[i]), Integer.parseInt(str[i+1]), Integer.parseInt(str[i+2]));
			}
			
			drawMazeLayout(clientID, 0);
		}
		
		// create ID NAME TREASURE X Y
		else if (str[0].equals("create")) {
			if (str[1].equals(clientID)) {
				player.setX(Integer.parseInt(str[4]));
				player.setY(Integer.parseInt(str[5]));
				maze.addPlayer(clientID, player);
			}
			else {
				Player p = new Player(str[2], Integer.parseInt(str[3]), Integer.parseInt(str[4]), Integer.parseInt(str[5]));
				maze.addPlayer(str[1], p);
			}
		}
		
		// set-player ID X Y NewTSnum totalSnum
		else if (str[0].equals("set-player")) {
			String id = str[1];
			int x = Integer.parseInt(str[2]);
			int y = Integer.parseInt(str[3]);
			int newTreasure = Integer.parseInt(str[4]);
			maze.getPlayer(id).setX(x);
			maze.getPlayer(id).setY(y);
			maze.clearTreasure(x, y);
			maze.getPlayer(id).addTreasure(newTreasure);
			
			// draw the game picture
			drawMazeLayout(id, newTreasure);
		}
		
		// set-bs IP PORT
		else if (str[0].equals("set-bs")) {
			try {
				bsSocket = new Socket(str[1], Integer.parseInt(str[2]));
				bsos = new PrintWriter(bsSocket.getOutputStream());
				bsos.println("set-id " + clientID);
				bsos.flush();
			} catch (UnknownHostException e) {
				e.printStackTrace();
			} catch (IOException e) {
				e.printStackTrace();
			}
		}
		
		// the PrimaryServer tell this client to be a BackupServer
		else if (str[0].equals("backup")) {
			if ( serverClient ) {
				os.println("backup-fail");
				os.flush();
			} else {
				Server serverThread = new Server(maze.getN(), maze.getM(), false);
				serverThread.start();
				int port = serverThread.getPort();
				while (port == 0) {
					port = serverThread.getPort();
					System.out.print('.');
				}
				System.out.println();
				serverClient = true;
				os.println("backup-ready " + port);
				os.flush();
			}
		}
		
		// the server want to find a backup server
		else if (str[0].equals("backup-search")) {
			// tell the server that here is a client (not a server client)
			if ( ! serverClient ) {
				os.println("backup-apply");
				os.flush();
			}
		}
	}
	
		
	private void drawMazeLayout(String pid, int add) {
		int n, i, j, val, myX, myY;
		String a_line = "   ";
		
		n = maze.getN();
		myX = player.getX();
		myY = player.getY();
		
		boolean [][] playerPos = new boolean[n][n];
		for (String id : maze.getPlayerIdSet()) {
			if ( ! id.equals(clientID) ) {
				playerPos[maze.getPlayer(id).getX()][maze.getPlayer(id).getY()] = true;
			}
		}
		
		// draw top side
		for(i=0; i<n; i++)
		{
		  a_line += String.valueOf(i);
		  if(i<10)  a_line += " ";
		  a_line += " ";
		} 		
		System.out.println(a_line + "x");
		
		// left side, draw treasure		
		for(i=0; i<n; i++)
		{
			a_line = ""; // clear
			a_line = String.valueOf(i);
			if(i<10)  
				a_line+= "  ";
			else 
				a_line+= " ";
			
			for(j=0; j<n; j++)
			{
			   val = maze.getTreasure(j, i);
			   if(val>0)
			   {
					if( i==myY && j==myX) // this player's position
						a_line += "*";
					else if (playerPos[j][i]) // other players' position
						a_line += "#";
				    else
				    	a_line += String.valueOf(val);
				  
					if(val<10)  a_line += " ";  
			   }
			   else
			   {
				   if( i==myY && j==myX) // this player's position
					   a_line += "*";
				   else if (playerPos[j][i]) // other players' position
						a_line += "#";
				   else
						a_line += ".";
				   a_line += " ";  
			   }
				  
			   a_line += " ";  
			}
			System.out.println(a_line);			
		}
		System.out.println("y");
		
		a_line = ""; //clear
		if (clientID.equals(pid)) {
			a_line += player.getName() +"   "+ "x=" + String.valueOf(myX) + "  y=" + String.valueOf(myY) 		
					+ "   New Treasure: " + String.valueOf(add) + "  Total Treasure: " + String.valueOf(player.getTreasure()) + "\n";
		}
		else {
			a_line += player.getName() +"   "+ "x=" + String.valueOf(myX) + "  y=" + String.valueOf(myY) 		
					+ "   New Treasure: " + 0 + "  Total Treasure: " + String.valueOf(player.getTreasure()) + "\n";
		}
		
		for (String id : maze.getPlayerIdSet()) {
			if (!id.equals(clientID)) {
				Player p = maze.getPlayer(id);
				if (id.equals(pid)) {
					a_line += p.getName() +"   "+ "x=" + p.getX() + "  y=" + p.getY()	
							+ "   New Treasure: " + String.valueOf(add) + "  Total Treasure: " + String.valueOf(p.getTreasure()) + "\n";
				}
				else {
					a_line += p.getName() +"   "+ "x=" + p.getX() + "  y=" + p.getY()	
							+ "   New Treasure: " + 0 + "  Total Treasure: " + String.valueOf(p.getTreasure()) + "\n";
				}
			}
		}
		
		System.out.print(a_line);
	}
}
